package com.mamingchao.concurrent.thread_communication2;

import java.util.Stack;

/**
 * 同步容器，看完老师的解释，应该是 读和写都需要加锁的；读线程与读线程也是竞争锁的关系
 * 所有的线程里，只能有一个线程读或者写
 *
 * synchronized 加在get 和 put方法上，锁的是【SyncContainerWithSynchronized】这个对象
 * 所以 Object monitor,不管是读线程还是写线程，同一时间只能有一个线程获取
 *
 * 其他等待的线程，都等待在一个队列里面
 *
 * 然后 题目里的 2个生产者，10个消费者，只是说场景是这样，而不是说 容器只能让2个线程生产
 * 10个容器消费
 *
 * 这里用synchronized  实现同步容器 有一个问题就是，notifyAll是唤醒 所有其他等待着的 线程
 * 因为 方法是synchronized ,只有一个线程在工作，其他所有的线程（不论是生产者还是消费者）都等待在一个队列里
 * 当notifyAll唤醒所有在等待队列里的线程的时候，那 put 方法里的notifyAll有可能唤醒的还是 生产者的线程
 *
 * 这跟 ReentrantLock 对比的话，就是说 synchronized 没有办法唤醒指定一组 线程；要不notify()唤醒指定的一个
 * 要么notifyAll()唤醒全部
 *
 * Created by mamingchao on 2020/9/20.
 */
public class SyncContainerWithSynchronized {

    /*
        容器固定容量 为10
     */
    final int capacity = 8;

    Stack stackContainer = new Stack();

    /**
     * 如果容器已满，阻塞等待
     * 容器的写方法
     * @param  o
     * @return true false
     */
    public synchronized void put(Object o) {
        /**
         * 想想为什么用while ，不用if？
         *   当 容器满了的时候，线程wait 就停住了；然后被生产者 notify后，往下走，就会再走到while check一下，因为怕这个时候别的线程 往容器里放了，
         *   所以还需要check一下
         *   这里 比如容器满了，消费者取了一个，notifyAll了，然后多个生产者线程同时往下走，我们知道 应该只能有一个生产者 能真正的 走下去
         */
        while (stackContainer.size() == capacity) {
            try {
                this.wait(); //effective java
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        stackContainer.push(o);
        this.notifyAll();
    }


    /**
     * 如果容器已空，消费者阻塞
     * 容器的读方法
     * @return true false
     */
    public synchronized Object get() {

        while (stackContainer.empty()) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        Object result = stackContainer.pop();
        this.notifyAll();
        return result;
    }


    public static void main(String[] args) {
        SyncContainerWithSynchronized sc = new SyncContainerWithSynchronized();

        for (int i = 0; i < 10; i++) {
            String threadName = "put thread--" + i;
            String value = i + "";
            new Thread(()->{
                sc.put(value);
                System.out.println(threadName + "  has put in " + value);
            }).start();
        }

        for (int i = 0; i < 20; i++) {
            String threadName = "get thread--" + i;
            new Thread(()->{
                Object v = sc.get();
                System.out.println(threadName + " has get " + v);
            }).start();
        }
    }
}
